<?php
/**
 * Author: Hancock
 * Email: 84622365@qq.com
 * Date: 3/28/2016
 * Des: 阿里移动端支付
 */
class alipay {
    /**
     * HTTPS形式消息验证地址
     */
    var $https_verify_url = 'https://mapi.alipay.com/gateway.do?service=notify_verify&';
    /**
     * HTTP形式消息验证地址
     */
    var $http_verify_url = 'http://notify.alipay.com/trade/notify_query.do?';
    private $alipay_config;
    private $log;

    public function __construct(){
        include_once(INC_PATH."ali/mobile/alipay.config.php");
        $this->alipay_config = $alipay_config;
        $this->log = new log();
    }

    /**
     * 获取支付参数
     * @param $params
     */
    public function getResponse($params){
        header('Access-Control-Allow-Origin: *');
        header('Content-type: text/plain');
        echo $this->getResponseUrl($params);
    }

    private function getResponseUrl($params){
        $parameter = array(
            'payment_type'   => '1',                           // 必填，支付类型
            'service'        => 'mobile.securitypay.pay',               // 必填，接口名称，固定值
            'partner'        => $this->alipay_config['partner'],        // 必填，合作商户号
            '_input_charset' => $this->alipay_config['input_charset'],// 必填，参数编码字符集
            'seller_id'      => $this->alipay_config['seller_id'],     // 必填，卖家支付宝账号
            'out_trade_no'   => $params['out_trade_no'],       // 必填，商户网站唯一订单号
            'subject'        => $params['subject'],             // 必填，商品名称
            'total_fee'      => $params['total_fee'],               // 必填，总金额，取值范围为[0.01,100000000.00]
            'body'           => $params['body'],                // 必填，商品详情
            'notify_url'     => $params['notify_url'],         // 可选，服务器异步通知页面路径
            'it_b_pay'       => '1d',                           // 可选，未付款交易的超时时间
        );
        //生成需要签名的订单
        $orderInfo = $this->createLinkstring($parameter);
        //签名
        $sign = $this->rsaSign($orderInfo);
        //生成订单
        return $orderInfo.'&sign="'.$sign.'"&sign_type="RSA"';
    }

    /**
     * 参数过滤
     * @param $params
     * @return array
     */
    private function paraFilter($params){
        $para_filter = array();
        while (list ($key, $val) = each ($params)) {
            if($key == "sign" || $key == "sign_type" || $val == "")continue;
            else	$para_filter[$key] = $params[$key];
        }
        return $para_filter;
    }

    /**
     * 对数组排序
     * @param $para 排序前的数组
     * return 排序后的数组
     */
    private function argSort($para) {
        ksort($para);
        reset($para);
        return $para;
    }

    /**
     * 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @param $para 需要拼接的数组
     * return 拼接完成以后的字符串
     */
    private function createLinkstring($para) {
        $arg  = "";
        while (list ($key, $val) = each ($para)) {
            $arg.=$key.'="'.$val.'"&';
        }
        //去掉最后一个&字符
        $arg = substr($arg,0,count($arg)-2);

        //如果存在转义字符，那么去掉转义
        if(get_magic_quotes_gpc()){$arg = stripslashes($arg);}

        return $arg;
    }

    /**
     * RSA签名
     * @param $data 待签名数据
     * @param $private_key_path 商户私钥文件路径
     * return 签名结果
     */
    private function rsaSign($data) {
        $priKey = file_get_contents($this->alipay_config['private_key_path']);
        $res = openssl_get_privatekey($priKey);
        openssl_sign($data, $sign, $res);
        openssl_free_key($res);
        //base64编码
        $sign = base64_encode($sign);
        $sign = urlencode($sign);
        return $sign;
    }

    /**
     * RSA验签
     * @param $data 待签名数据
     * @param $ali_public_key_path 支付宝的公钥文件路径
     * @param $sign 要校对的的签名结果
     * return 验证结果
     */
    private function rsaVerify($data, $ali_public_key_path, $sign)  {
        $pubKey = file_get_contents($ali_public_key_path);
        $res = openssl_get_publickey($pubKey);
        $result = (bool)openssl_verify($data, base64_decode($sign), $res);
        openssl_free_key($res);
        return $result;
    }

    /**
     * 针对notify_url验证消息是否是支付宝发出的合法消息
     * @return 验证结果
     */
    public function verifyNotify($params){
        if(empty($params)) {//判断POST来的数组是否为空
            return false;
        }
        else {
            //生成签名结果
            $isSign = $this->getSignVerify($params, $params["sign"]);
            //获取支付宝远程服务器ATN结果（验证是否是支付宝发来的消息）
            $responseTxt = 'false';
            if (! empty($params["notify_id"])) {$responseTxt = $this->getAliResponse($params["notify_id"]);}

            //写日志记录
            if ($isSign) {
            	$isSignStr = 'true';
            }
            else {
            	$isSignStr = 'false';
            }
            $log_text = "responseTxt=".$responseTxt."\n notify_url_log:isSign=".$isSignStr.",";
            $log_text = $log_text.$this->createLinkString($params);

            //验证
            //$responsetTxt的结果不是true，与服务器设置问题、合作身份者ID、notify_id一分钟失效有关
            //isSign的结果不是true，与安全校验码、请求时的参数格式（如：带自定义参数等）、编码格式有关
            if (preg_match("/true$/i",$responseTxt) && $isSign) {
                return true;
            } else {
                $this->log->balance_write($log_text, 4);
                return false;
            }
        }
    }

    /**
     * 获取返回时的签名验证结果
     * @param $para_temp 通知返回来的参数数组
     * @param $sign 返回的签名结果
     * @return 签名验证结果
     */
    private function getSignVerify($para_temp, $sign) {
        //除去待签名参数数组中的空值和签名参数
        $para_filter = $this->paraFilter($para_temp);

        //对待签名参数数组排序
        $para_sort = $this->argSort($para_filter);

        //把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
        $prestr = $this->createLinkstring($para_sort);

        $isSgin = false;
        switch (strtoupper(trim($this->alipay_config['sign_type']))) {
            case "RSA" :
                $isSgin = $this->rsaVerify($prestr, trim($this->alipay_config['ali_public_key_path']), $sign);
                break;
            default :
                $isSgin = false;
        }

        return $isSgin;
    }

    /**
     * 获取远程服务器ATN结果,验证返回URL
     * @param $notify_id 通知校验ID
     * @return 服务器ATN结果
     * 验证结果集：
     * invalid命令参数不对 出现这个错误，请检测返回处理中partner和key是否为空
     * true 返回正确信息
     * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
     */
    private function getAliResponse($notify_id) {
        $transport = strtolower(trim($this->alipay_config['transport']));
        $partner = trim($this->alipay_config['partner']);
        $verify_url = '';
        if($transport == 'https') {
            $verify_url = $this->https_verify_url;
        }
        else {
            $verify_url = $this->http_verify_url;
        }
        $verify_url = $verify_url."partner=" . $partner . "&notify_id=" . $notify_id;
        $responseTxt = $this->getHttpResponseGET($verify_url, $this->alipay_config['cacert']);

        return $responseTxt;
    }

    /**
     * 远程获取数据，GET模式
     * 注意：
     * 1.使用Crul需要修改服务器中php.ini文件的设置，找到php_curl.dll去掉前面的";"就行了
     * 2.文件夹中cacert.pem是SSL证书请保证其路径有效，目前默认路径是：getcwd().'\\cacert.pem'
     * @param $url 指定URL完整路径地址
     * @param $cacert_url 指定当前工作目录绝对路径
     * return 远程输出的数据
     */
    private function getHttpResponseGET($url,$cacert_url) {
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头
        curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 显示输出结果
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL证书认证
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证
        curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//证书地址
        $responseText = curl_exec($curl);
        //var_dump( curl_error($curl) );//如果执行curl过程中出现异常，可打开此开关，以便查看异常内容
        curl_close($curl);

        return $responseText;
    }
}
